//===============================================================================
// Microsoft patterns & practices
// Mobile Client Software Factory - July 2006
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom;
using Microsoft.Practices.Mobile.ObjectBuilder;
using System.Reflection;
using System.Configuration;

namespace Microsoft.Practices.ObjectBuilder.ObGen
{
	public abstract class PolicyGenerator : IPolicyGenerator
	{
		CodeExpression policies = new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), "Policies");
		List<string> references = new List<string>();
		List<string> imports = new List<string>();

		public virtual string[] AssemblyReferences
		{
			get { return references.ToArray(); }
		}

		public virtual string[] NamespaceImports
		{
			get { return imports.ToArray(); }
		}

		protected void AddAssemblyReference(string assemblyFileName)
		{
			if (!references.Contains(assemblyFileName))
			{
				references.Add(assemblyFileName);
			}
		}

		protected void AddAssemblyReference(Assembly assembly)
		{
			AddAssemblyReference(new Uri(assembly.CodeBase).LocalPath);
		}

		/// <summary>
		/// Adds an assembly reference to the assembly owning the type, as well 
		/// as all assemblies from types used as generic parameters to the 
		/// type, if any.
		/// </summary>
		protected void AddAssemblyReference(Type type)
		{
			AddAssemblyReference(type.Assembly);

			if (type.IsGenericType)
			{
				foreach (Type genericParam in type.GetGenericArguments())
				{
					AddAssemblyReference(genericParam);
				}
			}
		}

		protected void AddNamespaceImport(string import)
		{
			if (!imports.Contains(import))
			{
				imports.Add(import);
			}
		}

		public abstract CodeStatementCollection GenerateStatements(Type typeToProcess, CodeTypeDeclaration generatedType, NamingContext namingContext);

		protected CodeExpression PoliciesExpression
		{
			get { return policies; }
		}

		/// <summary>
		/// Policy generators should always use this method as it ensures policies are set properly, including using 
		/// the generated method <see cref="IPolicyProvider.GetPolicies"/> arguments to register the policies.
		/// </summary>
		protected CodeStatement SetPolicyStatement(Type policyInterface, CodeExpression policyExpression)
		{
			// policies.Set<ICreationPolicy>(new TypedConstructorPolicy(), typeToBuild, idToBuild);
			CodeMethodInvokeExpression setPolicy = new CodeMethodInvokeExpression(
				new CodeMethodReferenceExpression(
					PoliciesExpression,
					"Set",
					new SafeCodeTypeReference(policyInterface)),
				policyExpression, 
				new CodeArgumentReferenceExpression("typeToBuild"), 
				new CodeArgumentReferenceExpression("idToBuild"));

			return new CodeExpressionStatement(setPolicy);
		}

		protected CodeStatementCollection GenerateBlockComment(string comment)
		{
			CodeStatementCollection statements = new CodeStatementCollection();
			statements.Add(new CodeCommentStatement(String.Empty));
			statements.Add(new CodeCommentStatement(comment));
			statements.Add(new CodeCommentStatement(String.Empty));

			return statements;
		}

		/// <summary>
		/// Generates a field with <paramref name="variableName"/> that contains the 
		/// constructed attribute, with information extracted from the attribute declaration.
		/// </summary>
		protected CodeStatementCollection BuildAttributeCreationStatements(AttributeNode attribute, string variableName)
		{
			AddAssemblyReference(attribute.Type.GetRuntimeType());

			CodeStatementCollection attributeBuildup = new CodeStatementCollection();
			Type attributeType = attribute.Type.GetRuntimeType();
			AddNamespaceImport(attributeType.Namespace);

			//	FooInjectionAttribute attribute1 = new FooInjectionAttribute("foo", 25);

			CodeObjectCreateExpression createAttribute = new CodeObjectCreateExpression(attributeType.Name);
			CodeVariableDeclarationStatement attributeVar = new CodeVariableDeclarationStatement(
				attributeType.Name, variableName, createAttribute);
			attributeBuildup.Add(attributeVar);

			foreach (Expression expression in attribute.Expressions)
			{
				Literal literal = expression as Literal;
				NamedArgument named = expression as NamedArgument;

				if (literal != null)
				{
					CodeExpression valueExpression = GetValueExpression(literal);
					createAttribute.Parameters.Add(valueExpression);
				}
				else if (named != null)
				{
					literal = (Literal)named.Value;
					CodeExpression valueExpression = GetValueExpression(literal);

					// attribute1.Property = value;
					attributeBuildup.Add(new CodeAssignStatement(
						new CodePropertyReferenceExpression(
							new CodeVariableReferenceExpression(variableName),
							named.Name.Name),
							valueExpression));
				}
			}
			return attributeBuildup;
		}

		private CodeExpression GetValueExpression(Literal literal)
		{
			CodeExpression valueExpression;
			Type runtimeType = literal.Type.GetRuntimeType();

			if (runtimeType == typeof(Type))
			{
				valueExpression = new CodeTypeOfExpression(runtimeType);
				AddAssemblyReference(runtimeType);
			}
			else if (runtimeType.IsEnum)
			{
				AddNamespaceImport(runtimeType.Namespace);
				// Enums are serialized with their primitive underlying value. Need to cast.
				valueExpression = new CodeCastExpression(runtimeType.Name, new CodePrimitiveExpression(literal.Value));
			}
			else
			{
				valueExpression = new CodePrimitiveExpression(literal.Value);
			}
			return valueExpression;
		}
	}
}
